﻿using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Linq;
using MathService;
using DST = System.Diagnostics.DebuggerStepThroughAttribute;

namespace System
{
    public static class ObjectExtentions
    {
        public class ObjectSelector<TSource, TResult>
        {
            public bool Continue { get; set; }
            public TSource Object { get; set; }
            public TResult Result { get; set; }
            public int Iteration { get; set; }
            public ObjectSelector() { Continue = true; }

            public void Next(TResult Result, bool Continue = true)
            {
                this.Result = Result;
                this.Continue = Continue;
            }
        }

        public static IEnumerable<TResult> SelectObj<TSource, TResult>(this TSource source,
            Action<ObjectSelector<TSource, TResult>> Selector)
        {
            Contract.Requires(!ReferenceEquals(Selector, null));
            var selector = new ObjectSelector<TSource, TResult>
            {
                Object = source,
                Result = default(TResult)
            };
            do
            {
                Selector(selector);
                yield return selector.Result;
                selector.Result = default(TResult);
                selector.Iteration++;
            } while(selector.Continue);
        }

        public static TResult SelectObject<TSource, TResult>(this TSource source, Func<TSource, TResult> Selector)
        {
            Contract.Requires(!ReferenceEquals(Selector, null));
            return Selector(source);
        }

        public static string ToFormattedString(this object obj, string Format = "{0}")
        {
            Contract.Requires(!ReferenceEquals(obj, null));
            return string.Format(Format, obj);
        }

        public static string ToString<T>(this T t, Func<T, string> converter)
        {
            Contract.Requires(converter != null);
            return converter(t);
        }

        public static int GetHashCode(params object[] Objects)
        {
            Contract.Requires(Objects != null);
            Contract.Requires(Objects.Length > 0);
            return Objects.GetComplexHashCode();
        }
        public static int GetComplexHashCode(this object[] Objects)
        {
            Contract.Requires(!ReferenceEquals(Objects, null));
            Contract.Requires(Objects.Length > 0);
            unchecked
            {
                var result = Consts.BigPrime_int; //397;
                for(var i = 0; i < Objects.Length; i++)
                    result = (result * 397) ^ Objects[i].GetHashCode();
                return result;
            }
        }

        [DST, Pure]
        public static TOut ConvertObjectTo<TIn, TOut>(this TIn o, Converter<TIn, TOut> Converter)
        {
            Contract.Requires(!ReferenceEquals(Converter, null));
            return Converter(o);
        }

        [DST, Pure]
        public static TAttribute GetObjectAttribute<TAttribute>(this object o, bool Inherited = false)
            where TAttribute : Attribute
        {
            Contract.Requires(!ReferenceEquals(o, null));
            var objectAttributes = o.GetObjectAttributes<TAttribute>(Inherited);
            return objectAttributes.Length != 0 ? objectAttributes[0] : default(TAttribute);
        }

        [DST, Pure]
        public static TAttribute[] GetObjectAttributes<TAttribute>(this object o, bool Inherited = false)
            where TAttribute : Attribute
        {
            Contract.Requires(!ReferenceEquals(o, null));
            return o.GetType().GetCustomAttributes<TAttribute>(Inherited);
        }

        [DST, Pure]
        public static bool IsNotNull(this object o)
        {
            Contract.Ensures(Contract.Result<bool>() != ReferenceEquals(o, null));
            return !ReferenceEquals(o, null);
        }

        [DST, Pure]
        public static bool IsNull(this object o)
        {
            Contract.Ensures(Contract.Result<bool>() == ReferenceEquals(o, null));
            return ReferenceEquals(o, null);
        }

        [DST, Pure]
        public static TAttribute[] GetAttributes<TAttribute, TObject>(this TObject o, bool Inherited = false)
            where TAttribute : Attribute
        {
            Contract.Requires(!ReferenceEquals(o, null));
            return typeof(TObject).GetCustomAttributes<TAttribute>(Inherited);
        }

        /// <summary>Инициализировать объект ссылочного типа</summary>
        /// <typeparam name="T">Тип объекта</typeparam>
        /// <param name="obj">Инициализируемый объект</param>
        /// <param name="Initializator">Метод инициализации</param>
        /// <returns>Инициализированный объект</returns>
        [DST]
        public static T InitializeObject<T>(this T obj, Action<T> Initializator) where T : class
        {
            Contract.Requires(!ReferenceEquals(obj, null));
            Contract.Ensures(!ReferenceEquals(Contract.Result<T>(), null));
            Contract.Ensures(ReferenceEquals(Contract.Result<T>(), obj));
            if(Initializator != null) Initializator(obj);
            return obj;
        }

        public static T InitializeObject<T>(this T obj, Func<T, T> Initializator) where T : class
        {
            Contract.Ensures(Initializator == null && ReferenceEquals(Contract.Result<T>(), obj));
            return Initializator != null ? Initializator(obj) : obj;
        }

        [DST]
        public static void ToConsole<T>(this T Obj)
        {
            if(ReferenceEquals(Obj, null)) return;
            Console.Write(Obj);
        }

        [DST]
        public static void ToConsole<TObject>(this TObject Obj, string Format)
        {
            if(ReferenceEquals(Obj, null)) return;
            Console.Write(Format, Obj);
        }

        [DST]
        public static void ToConsoleLN<T>(this T Obj)
        {
            if(ReferenceEquals(Obj, null)) return;
            Console.WriteLine(Obj);
        }

        [DST]
        public static void ToConsoleLN<T>(this T Obj, string Format)
        {
            if(ReferenceEquals(Obj, null)) return;
            Console.WriteLine(Format, Obj);
        }

        [DST, Conditional("DEBUG")]
        public static void ToDubugOut<T>(this T Obj)
        {
            if(ReferenceEquals(Obj, null)) return;
            Debug.Write(Obj);
        }

        [DST, Conditional("DEBUG")]
        public static void ToDubugOut<T>(this T Obj, bool Condition)
        {
            if(ReferenceEquals(Obj, null)) return;
            Debug.WriteIf(Condition, Obj);
        }

        [DST, Conditional("DEBUG")]
        public static void ToDubugOutLN<T>(this T Obj)
        {
            if(ReferenceEquals(Obj, null)) return;
            Debug.WriteLine(Obj);
        }

        [DST, Conditional("DEBUG")]
        public static void ToDubugOutLN<T>(this T Obj, bool Condition)
        {
            if(ReferenceEquals(Obj, null)) return;
            Debug.WriteLineIf(Condition, Obj);
        }
    }
}

namespace System.Tags
{
    public static class TagExtentions
    {
        [DST]
        public static TTag GetTag<TTag>(this object o)
        {
            Contract.Requires(o != null);
            return TagPool.Tag<TTag>(o);
        }

        [DST]
        public static void SetTag<TTag>(this object o, TTag Tag)
        {
            Contract.Requires(o != null);
            TagPool.SetTag(o, Tag);
        }

        #region Nested type: TagPool

        private static class TagPool
        {
            private static readonly object sf_Lock = new object();
            private static Dictionary<WeakReference, Dictionary<Type, object>> sf_Tags;

            //static TagPool()
            //{
            //}

            public static void SetTag(object o, object Tag)
            {
                lock(sf_Lock)
                {
                    var tags = sf_Tags ?? (sf_Tags = new Dictionary<WeakReference, Dictionary<Type, object>>());

                    tags.Keys.Where(w => !w.IsAlive).ToArray().Foreach(w => tags.Remove(w));
                    Predicate<WeakReference> selector = w => o.Equals(w.Target);

                    var reference = tags.Keys.Find(selector);
                    Dictionary<Type, object> dictionary;
                    if(reference != null)
                        dictionary = tags[reference];
                    else
                        tags.Add(new WeakReference(o), dictionary = new Dictionary<Type, object>());

                    var type = Tag.GetType();

                    if(dictionary.Keys.Contains(type))
                        dictionary[type] = Tag;
                    else
                        dictionary.Add(type, Tag);
                }
            }

            public static TTagType Tag<TTagType>(object o)
            {
                lock(sf_Lock)
                {
                    if(sf_Tags == null)
                        return default(TTagType);

                    var tags = sf_Tags;
                    Func<WeakReference, bool> func = w => !w.IsAlive;
                    tags.Keys.Where(func).Foreach(w => tags.Remove(w));
                    Predicate<WeakReference> selector = w => o.Equals(w.Target);
                    var reference = tags.Keys.Find(selector);
                    if(reference == null)
                        return default(TTagType);
                    var dictionary = tags[reference];
                    object obj2;
                    if(!dictionary.TryGetValue(typeof(TTagType), out obj2))
                        return default(TTagType);
                    return (TTagType)obj2;
                }
            }
        }

        #endregion
    }
}